#include "device.h"

#include <fcntl.h>
#include <malloc.h>
#include <string.h>
#include <unistd.h>

#include "router.h"
#include "thread_pool.h"

#include "log.h"

static void *input(void *pv);

device *device_create(const char *file_name, const char *device_name,
					  struct message *(*read)(void *device),
					  int32_t (*write)(void *device,
									   const struct message *message)) {
	if (!file_name) {
		goto file_name_is_null;
	}
	char *_file_name = strdup(file_name);
	if (!_file_name) {
		goto alloc_file_name_fail;
	}
	int fd = open(_file_name, O_RDWR | O_CREAT);
	if (fd == -1) {
		goto open_device_fail;
	}
	if (!device_name) {
		goto device_name_is_null;
	}
	char *_device_name = strdup(device_name);
	if (!_device_name) {
		goto alloc_device_name_fail;
	}
	device *device = (_device *)calloc(1, sizeof(_device));
	if (!device) {
		goto alloc_device_fail;
	}
	device->file_name = _file_name;
	device->file_descriptor = fd;
	device->device_name = (char *)device_name;
	device->input_thread = pthread_t_no_thread;
	device->output_thread = pthread_t_no_thread;
	device->read = read;
	device->write = write;
	return device;

alloc_device_fail:
	free(_device_name);
alloc_device_name_fail:
device_name_is_null:
	close(fd);
open_device_fail:
alloc_file_name_fail:
file_name_is_null:
	return NULL;
}
int32_t device_start(device *device, uint32_t flag) {
	if (!device) {
		return -1;
	}
	if ((flag & device_flag_input) &&
		(device->input_thread == pthread_t_no_thread)) {
		if (pthread_create(&device->input_thread, NULL, input, device)) {
			return -1;
		}
	}
	if (flag & device_flag_output) {
		device->output_thread = pthread_t_allow_thread;
	}
	return 0;
}
int32_t device_stop(device *device, uint32_t flag) {
	if (!device) {
		return -1;
	}
	if ((flag & device_flag_input) &&
		(device->input_thread != pthread_t_no_thread)) {
		if (pthread_cancel(device->input_thread) ||
			pthread_join(device->input_thread, NULL)) {
			return -1;
		}
		device->input_thread = pthread_t_no_thread;
	}
	if (flag & device_flag_output) {
		device->output_thread = pthread_t_no_thread;
	}
	return 0;
}
device *device_destory(device *device) {
	if (!device) {
		return NULL;
	}
	device_stop(device, device_flag_input | device_flag_output);
	close(device->file_descriptor);
	free(device);
	return NULL;
}

static void *input(void *pv) {
	device *device = (_device *)pv;
	struct message *message = NULL;
	while (1) {
		message = device->read(device);
		if (message) {
			thread_pool_run(router_task, message);
		}
	}
	return NULL;
}
